Skip to content

S03-02 核心类-包装类 ​

[TOC]

包装类 ​

在 Java 中,包装类(Wrapper Class) 是一个非常基础且核心的概念。Java 是一种面向对象的语言,但为了性能考量,它保留了 8 种基本数据类型(如 int, double 等),这些基本类型并不是对象。

为了让基本数据类型也能拥有对象的特征(例如参与面向对象的操作、调用方法、放入集合中),Java 为每一种基本数据类型都提供了一个对应的包装类。

基本数据类型 vs 包装类 ​

基本数据类型与包装类的对应关系:

Java 的 java.lang 包中包含了 8 种基本数据类型的包装类。它们的对应关系非常简单,除了 int 和 char 之外,其他的只需将首字母大写即可:

基本数据类型 (Primitive Type)包装类 (Wrapper Class)父类
byteByteNumber
shortShortNumber
intIntegerNumber
longLongNumber
floatFloatNumber
doubleDoubleNumber
charCharacterObject
booleanBooleanObject

为什么需要包装类 ​

为什么需要包装类:

既然有了基本数据类型,为什么还要多此一举设计包装类呢?主要有以下几个原因:

  1. 集合框架(Collections)的限制:Java 中的集合(如 List, Set, Map)只能存储对象,不能直接存储基本数据类型。如果你想创建一个包含整数的列表,只能写 List<Integer>,而不能写 List<int>。

  2. 表示“空值”(null):基本数据类型有默认值(如 int 默认是 0),但无法表示“没有值”的状态。而在实际开发(尤其是数据库操作和 Web 请求)中,0 和 null 的意义完全不同。包装类作为对象,可以被赋值为 null。

  3. 提供丰富的实用方法和常量:包装类中封装了大量有用的方法。例如,Integer.parseInt("123") 可以将字符串转为整数,Integer.MAX_VALUE 可以直接获取 int 类型的最大值。

自动装箱与自动拆箱 ​

自动装箱(Autoboxing)与自动拆箱(Unboxing):

在 Java 5 之前,基本类型和包装类之间的转换需要手动进行,代码显得非常繁琐。从 Java 5 开始,引入了自动装箱和自动拆箱机制。

  • 自动装箱:Java 编译器自动将基本数据类型转换为对应的包装类。

    • 底层原理:编译器在编译时调用了包装类的 valueOf() 方法。
  • 自动拆箱:Java 编译器自动将包装类转换为对应的基本数据类型。

    • 底层原理:编译器在编译时调用了包装类的 Number 超类的 xxxValue() 方法(如 intValue())。

示例:

  1. Java 5 之前的写法(手动)

    java
    // Java 5 之前的写法(手动)
    Integer numObj = Integer.valueOf(100); // 装箱
    int num = numObj.intValue();           // 拆箱
  2. Java 5 之后的写法(自动装箱与拆箱)

    java
    // Java 5 之后的写法(自动装箱与拆箱)
    Integer a = 10;  // 自动装箱:等同于 Integer.valueOf(10);
    int b = a;       // 自动拆箱:等同于 a.intValue();
    
    // 甚至可以在数学运算中自动混用
    Integer x = 5; 
    Integer y = x + 5; // 先把 x 拆箱变成 5,相加得到 10,再把 10 自动装箱赋值给 y

内存结构变化 ​

转换成包装类后,内存结构有如下变化

java
public static void main(String[] args) {
  int num = 520;
  Integer obj = new Integer(520);
}

image-20260317165958118

常量池缓存 ​

包装类的常量池缓存机制(面试高频考点):

为了提高性能并减少内存空间的占用,Java 在 Byte, Short, Integer, Long, Character, Boolean 这几个包装类中实现了缓存机制(Cache)。

以最常用的 Integer 为例,Java 内部维护了一个 IntegerCache,默认缓存了 -128 到 127 之间的 Integer 对象。

当你通过自动装箱(即 Integer.valueOf(i))创建一个在该范围内的对象时,Java 会直接从缓存池中返回已经存在的对象,而不会 new 一个新对象;如果超出这个范围,则会去堆内存中 new 一个新对象。

java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true (都在 -128 ~ 127 范围内,指向缓存池中的同一个对象)

Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false (超出了范围,底层每次都 new 了一个新对象,内存地址不同)

注意:Double 和 Float 并没有实现缓存机制,因为在任何范围内,浮点数都有无限多个。

包装类比较 ​

包装类比较时的注意事项:

因为包装类是对象,所以在进行相等性比较时,必须非常小心:

  1. 比较值是否相等,必须使用 .equals() 方法,千万不要使用 ==(除非你在比较基本数据类型)。== 比较的是对象的内存地址,而 .equals() 被重写过,比较的是对象内部包裹的值。

    java
    Integer i1 = new Integer(10);
    Integer i2 = new Integer(10);
    System.out.println(i1 == i2);      // false (new 出来的对象地址一定不同)
    System.out.println(i1.equals(i2)); // true (值相等)
  2. 基本类型与包装类用 == 比较时,包装类会自动拆箱:

    java
    int num1 = 200;
    Integer num2 = 200;
    System.out.println(num1 == num2); // true (num2 会自动拆箱为 int,然后比较数值)

常见类型转换 ​

常见类型转换操作:

包装类在字符串与基本类型之间的转换中扮演着重要角色:

  1. 字符串转基本类型:

    使用包装类的 parseXxx(String s) 方法(最常用):

    java
    int i = Integer.parseInt("123");
    double d = Double.parseDouble("3.14");
  2. 字符串转包装类对象:

    使用 valueOf(String s) 方法:

    java
    Integer num = Integer.valueOf("123");
  3. 基本类型转字符串:

    除了直接用 "" + 123 这种方式外,还可以用 String.valueOf():

    java
    String s1 = String.valueOf(123); // "123"

image-20260324152747528

自定义包装类 ​

image-20260317170309954

练习题 ​

练习题:

利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。

提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。

1、创建Vector对象:Vector v=new Vector();

2、给向量添加元素:v.addElement(Object obj); //obj必须是对象

3、取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。

4、计算向量的长度:v.size();

5、若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等

Integer ​

在 Java 日常开发中,Integer 是使用频率极高的包装类。除了作为 int 的对象形态存在,它内部还封装了大量极其好用的静态方法和常量,涵盖了类型转换、进制转换、数值比较和位运算等场景。

常量 ​

核心常量:

Integer 类提供了一些常量,可以让你直接获取 int 类型的物理属性,231−12^{31}-1 避免硬编码:

常量名称描述实际值
Integer.MAX_VALUEint 类型能表示的最大值2147483647 (231−12^{31}-1)
Integer.MIN_VALUEint 类型能表示的最小值-2147483648 (−231-2^{31})
Integer.SIZEint 值在内存中占用的位数(Bit)32
Integer.BYTESint 值在内存中占用的字节数(Byte)4 (Java 8 引入)

(提示:在求数组最大值/最小值算法的初始化时,经常会用到 MIN_VALUE 和 MAX_VALUE。)

方法 ​

字符串转数字 ​

这是日常开发中最频繁的操作,主要处理前端传来的字符串数据或进行日志打印。

  • int Integer.parseInt():(String s),将字符串解析为基本数据类型 int。如果字符串不是合法的数字(例如包含字母),会抛出 NumberFormatException。

    java
    int num = Integer.parseInt("1024");
  • int Integer.parseInt():(String s, int radix),将指定进制的字符串解析为十进制的 int。

    java
    int num = Integer.parseInt("FF", 16); // 将 16 进制的 FF 转为 10 进制,结果是 255
  • Integer Integer.valueOf():(String s),将字符串解析为包装类 Integer 对象。底层其实就是调用了 parseInt,然后再进行装箱。

数字转字符串 ​

  • String Integer.toString():(int i),将基本类型 int 转换为字符串。

    java
    String s = Integer.toString(100); // 相当于 String.valueOf(100)

进制转换 ​

如果你需要将一个十进制整数打印成二进制、八进制或十六进制字符串,Integer 提供了直接的静态方法,无需自己写取余算法:

  • String Integer.toBinaryString():(int i),转为二进制字符串。

  • String Integer.toOctalString():(int i),转为八进制字符串。

  • String Integer.toHexString():(int i),转为十六进制字符串。

    java
    int num = 28;
    System.out.println(Integer.toBinaryString(num)); // "11100"
    System.out.println(Integer.toHexString(num));    // "1c"

对象的创建与缓存 ​

  • Integer Integer.valueOf():(int i),这是目前官方唯一推荐的创建 Integer 对象的方式。

    避坑指南:自 Java 9 起,构造方法 new Integer(10) 已被标记为废弃(Deprecated)。因为 valueOf 方法内部使用了前面提到的 -128 到 127 的缓存池机制,性能更好,内存占用更低。

大小比较 ​

  • int compareTo():(Integer anotherInteger),实例方法。比较两个 Integer 对象的值。返回 0(相等)、< 0(小于)、> 0(大于)。

  • int Integer.compare():(int x, int y),静态方法。直接比较两个基本类型 int 的大小。返回值同上。

    java
    System.out.println(Integer.compare(10, 20)); // 返回 -1

数学运算@J8 ​

为了配合 Lambda 表达式和 Stream API(如 reduce 操作),Java 8 在 Integer 中新增了几个静态算术方法:

  • int Integer.max():(int a, int b),返回两者中较大的值。

  • int Integer.min():(int a, int b),返回两者中较小的值。

  • int Integer.sum():(int a, int b),返回两数之和。

底层位运算 ​

在一些追求极致性能的算法、源码或密码学领域,经常会用到 Integer 提供的位级操作方法:

  • int Integer.bitCount():(int i),返回指定 int 值的二进制补码表示中,1 的个数。(LeetCode 常考题)。

    java
    // 7 的二进制是 0000 0111,里面有 3 个 1
    System.out.println(Integer.bitCount(7)); // 输出: 3
  • int Integer.reverse():(int i),将二进制位的顺序完全反转。

  • int Integer.highestOneBit():(int i),保留最高位的 1,其余全置为 0(常用于 HashMap 容量计算的底层逻辑中)。

总结避坑 ​

当你在业务代码中比较两个 Integer 对象的值是否相等时,永远使用 .equals() 方法,绝不要使用 ==。这是因为缓存池(-128 ~ 127)的存在会让 == 在小数值时表现正常,一旦超过这个范围,== 就会因为内存地址不同而返回 false,引发难以排查的 Bug。

Character ​

在 Java 中,Character 是基本数据类型 char 的包装类。由于 char 在 Java 中占用 2 个字节(16 位),本质上是一个无符号的整数(对应 Unicode 字符集),因此 Character 类不仅提供了对象化的包装,还封装了大量用于字符分类、判断和转换的实用静态方法。

方法 ​

字符类型判断 ​

在处理字符串解析、表单验证或算法题(如验证回文串)时,我们经常需要判断一个字符是字母、数字还是空格。Character 提供了一系列 isXxx() 方法:

方法声明(static int)功能描述示例与返回值
isDigit(char ch)判断是否为数字 (0-9)isDigit('5') →\rightarrow true
isLetter(char ch)判断是否为字母 (包含中文字符等 Unicode 字母)isLetter('A') →\rightarrow true
isLetter('中') →\rightarrow true
isLetterOrDigit(char ch)判断是否为字母或数字isLetterOrDigit('!') →\rightarrow false
isWhitespace(char ch)判断是否为空白字符(空格、Tab \t、换行 \n 等)isWhitespace(' ') →\rightarrow true
isUpperCase(char ch)判断是否为大写字母isUpperCase('a') →\rightarrow false
isLowerCase(char ch)判断是否为小写字母isLowerCase('A') →\rightarrow false

注意:isLetter 不仅仅识别 26 个英文字母,它基于 Unicode 标准,因此判断汉字等其他语言的字母字符时也会返回 true。

大小写转换 ​

如果需要统一字符的格式,可以使用以下两个转换方法。如果传入的字符本身不是字母,或者已经是要转换的大小写,方法会直接返回原字符:

  • char toUpperCase():(char ch),将字符转换为大写。

  • char toLowerCase():(char ch),将字符转换为小写。

java
char c1 = Character.toUpperCase('a'); // 返回 'A'
char c2 = Character.toLowerCase('B'); // 返回 'b'
char c3 = Character.toUpperCase('5'); // 返回 '5' (非字母不改变)

字符与数字的转换 ​

这是一个非常常见的易错点:如果你有一个字符 '9',直接强制转换为 int,得到的是它的 ASCII 码值 57,而不是数字 9。

  • int getNumericValue():(char ch),获取字符对应的真实数值。返回指定字符表示的 int 值。

    java
    char ch = '9';
    System.out.println((int) ch); // 输出 57 (这是 ASCII 码)
    System.out.println(Character.getNumericValue(ch)); // 输出 9 (这是真实的数值)

    进阶小技巧(减去 '0'):

    在算法中,更高效且常见的做法是直接利用字符的 ASCII 码差值来获取数字:

    java
    char ch = '7';
    int num = ch - '0'; // 底层是 55 - 48,结果就是 7。比调用方法更快!

缓存机制与对象创建 ​

Character 的缓存机制与对象创建:

与 Integer 类似,Character 也有为了节省内存而设计的常量池缓存机制。

  • 缓存范围:Character 缓存了从 \u0000 到 \u007F(即 0 到 127)的字符,这刚好覆盖了所有标准的 ASCII 字符。

  • 推荐创建方式:Character.valueOf(char c)

    自 Java 9 起,new Character('A') 同样被废弃,官方推荐使用 valueOf()。

java
Character c1 = Character.valueOf('A'); // ASCII 为 65
Character c2 = Character.valueOf('A');
System.out.println(c1 == c2); // true (命中 0-127 的缓存池,地址相同)

Character c3 = Character.valueOf('中'); // Unicode 远超 127
Character c4 = Character.valueOf('中');
System.out.println(c3 == c4); // false (超出缓存范围,创建了新对象)
System.out.println(c3.equals(c4)); // true (值相等)

处理复杂字符 ​

处理复杂字符(Emoji 与补充字符):

这是 Character 类较深的应用场景。标准的 char 是 16 位的,只能表示 Unicode 的基本多语言面(BMP)。但是,像 Emoji 表情(如 😭)或者某些生僻字的 Unicode 编码超过了 16 位,一个 char 是存不下的。

因此,Java 使用两个 char(称为代理对,Surrogate Pair) 来表示这些复杂的字符。

为了处理这种情况,Character 提供了支持传入 int codePoint(代码点,即真实的 Unicode 编码数值)的重载方法:

  • static boolean isSupplementaryCodePoint(int codePoint):判断是否为增补字符(如 Emoji)。
  • static int charCount(int codePoint):判断该字符需要几个 char 来表示(通常是 1 或 2)。